package com.hmdp.service.impl;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.security.Key;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {


    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private CacheClient cacheClient;

    ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public Shop queryById(Long id) throws JsonProcessingException, InterruptedException {
        //雪崩
//        Shop shop = queryWithPassThrough(id);

//        互斥锁
//        Shop shop = queryWithLock(id);
//        Shop shop = queryWithLogicExpire(id);
//        public <R, ID> R queryWithPassThrough(String head, ID id, Class<R> type, Function<ID, R> dback, Long time, TimeUnit unit) throws JsonProcessingException {

        Shop shop = cacheClient.queryWithPassThrough(RedisConstant.Shop_store, id, Shop.class, id2 -> getById(id2), 20L, TimeUnit.MINUTES);

//        Shop shop = cacheClient.queryWithLogicExpire(RedisConstant.Shop_store, id, Shop.class, id2 -> getById(id2));
        if (shop != null) {
            return shop;
        }
        throw new HmdpException(ResultCodeEnum.shop_Not);
    }


    //    雪崩
    public Shop queryWithPassThrough(Long id) throws JsonProcessingException {
        String shopJSon = stringRedisTemplate.opsForValue().get(RedisConstant.Shop_store + id);
// 注册 JavaTimeModule 以支持 LocalDateTime 反序列化
        objectMapper.registerModule(new JavaTimeModule());

        if ("".equals(shopJSon)) {
//            redis有数据。命中空字符。
            throw new HmdpException(ResultCodeEnum.shop_Not);
        }

        if (StringUtils.isNotBlank(shopJSon)) {
//            redis有数据。命中
            Shop shop = objectMapper.readValue(shopJSon, Shop.class);
            return shop;
        }
        Shop shop = getById(id);

        if (shop == null) {
//            穿透了.
            stringRedisTemplate.opsForValue().set(RedisConstant.Shop_store + id, "", 20, java.util.concurrent.TimeUnit.MINUTES);
            throw new HmdpException(ResultCodeEnum.shop_Not);
        }
        String shopString = objectMapper.writeValueAsString(shop);
        stringRedisTemplate.opsForValue().set(RedisConstant.Shop_store + id, shopString);

        stringRedisTemplate.expire(RedisConstant.Shop_store + id, new Random().nextInt(100) + 10, java.util.concurrent.TimeUnit.MINUTES);
        return shop;
    }


    //互斥锁。
    public Shop queryWithLock(Long id) throws JsonProcessingException, InterruptedException {
        String key = RedisConstant.Shop_store + id;
        String shopJSon = stringRedisTemplate.opsForValue().get(key);
// 注册 JavaTimeModule 以支持 LocalDateTime 反序列化
        objectMapper.registerModule(new JavaTimeModule());

        if ("".equals(shopJSon)) {
//            redis有数据。命中空字符。
            throw new HmdpException(ResultCodeEnum.shop_Not);
        }

        if (StringUtils.isNotBlank(shopJSon)) {
//            redis有数据。命中
            Shop shop = objectMapper.readValue(shopJSon, Shop.class);
            return shop;
        }
//            没有找到合理key数据。要去数据库找。
        String lockKey = RedisConstant.CACHE_Shop_Lock + id;
        boolean isLock = tryLock(lockKey);

        Shop shop = null;
        try {
            if (!isLock) {
                //           加锁失败
                Thread.sleep(500);
                return queryWithPassThrough(id);
            }
//加锁成功继续执行
            shop = getById(id);
            if (shop == null) {
                //            穿透了.
                stringRedisTemplate.opsForValue().set(key, "", 20, TimeUnit.MINUTES);
                throw new HmdpException(ResultCodeEnum.shop_Not);
            }
//没有穿透继续执行。
            String shopString = objectMapper.writeValueAsString(shop);
            stringRedisTemplate.opsForValue().set(key, shopString);
            // 生成一个介于0到100之间的随机整数
            Random random = new Random();

// 生成一个0到99之间的随机整数,防止redis雪崩。
            int randomInt = random.nextInt(100);
            stringRedisTemplate.expire(key, 100 + randomInt, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        } catch (HmdpException e) {
            throw new RuntimeException(e);
        } finally {
            unLock(lockKey);
        }
        return shop;
    }


    /**
     * 判断你的key是否存在，存在返回，不存在的话 查看锁的状态，没有锁的话，加锁---查询这个key的数据---解锁。
     *
     * @param id
     * @return
     */
    public boolean tryLock(String id) {
//如果有锁。
        boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(id, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    @Resource
    public IShopService shopService;

    public void unLock(String id) {
        stringRedisTemplate.delete(id);
    }

    @Override
    @Transactional
    public void updateShop(Shop shop) {
        boolean b = this.updateById(shop);
        stringRedisTemplate.delete(RedisConstant.Shop_store + shop.getId());
    }

    @Override
    public List<Shop> queryShopByType(Integer typeId, Integer current, Double x, Double y) {
        // 1. 判断是否是根据坐标查询。
        if (x != null && y != null) {
            // 根据类型分页查询
            // 定义中心点
            Point center = new Point(x, y);

            // 定义查询范围，单位是公里（Metrics.KILOMETERS）
            Circle circle = new Circle(center, new Distance(25, Metrics.KILOMETERS));

            long from = SystemConstants.DEFAULT_PAGE_SIZE * (current - 1);
            long to = SystemConstants.DEFAULT_PAGE_SIZE;
//            加上距离显示。

            RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance();
            // 执行 GEORADIUS 命令
//            找出这个圆圈范围内的    所有集合坐标。
            GeoResults<RedisGeoCommands.GeoLocation<String>> radius = stringRedisTemplate.opsForGeo()
                    .radius(RedisConstant.Shop_geo_type + typeId, circle, args);

            if (radius == null || radius.getContent().isEmpty()) {
                return Collections.emptyList();
            }
//        存储所有符合条件的商店id。
//            还有map。

            List<String> ids = new ArrayList<>();
            Map<String, Double> distanceMap = new HashMap<>();

            radius.getContent().stream()
                    .skip(from)
                    .limit(to)
                    .forEach(item -> {
                        String shopIdStr = item.getContent().getName();
                        ids.add(shopIdStr);
                        distanceMap.put(shopIdStr, item.getDistance().getValue());
                    });

            // 如果 ids 为空，直接返回
            if (ids.isEmpty()) {
                return Collections.emptyList();
            }

            // 构造 SQL 查询，处理 `FIELD` 中的字符串格式
            // 如果 `id` 是字符串类型，需要在 `FIELD` 中加上引号
            String fieldInClause = ids.stream()
                    .map(id -> "'" + id + "'")  // 为每个字符串添加引号
                    .collect(Collectors.joining(","));

            // 使用 MyBatis-Plus 的 in() 方法和 MySQL 的 Order By Field 进行排序
            List<Shop> shops = query()
                    .in("id", ids)    // 在数据库中根据 ID 查询
                    .last("ORDER BY FIELD(id, " + fieldInClause + ")")  // 按顺序排序
                    .list();

            // 将距离信息填充到结果中
            shops.stream().forEach(shop -> {
                shop.setDistance(distanceMap.get(shop.getId().toString()) * 1000);
            });

            return shops;
        }

        // 如果没有坐标，进行普通分页查询
        Page<Shop> page = shopService.query()
                .eq("type_id", typeId)
                .page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));
        return page.getRecords();
    }


//    逻辑过期的设置redis

    public void saveShop2Redis(Long id, long expireSeconds) throws JsonProcessingException {
        Shop shop = getById(id);
        if (shop == null) {
            stringRedisTemplate.opsForValue().set(RedisConstant.Shop_store + id, "", 20, TimeUnit.MINUTES);
        }
        RedisData redisData = new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
        String s = objectMapper.writeValueAsString(redisData);
        stringRedisTemplate.opsForValue().set(RedisConstant.Shop_store + id, s);
    }


    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    //    逻辑过期
    public Shop queryWithLogicExpire(Long id) throws JsonProcessingException {

        String shopJSon = stringRedisTemplate.opsForValue().get(RedisConstant.Shop_store + id);
// 注册 JavaTimeModule 以支持 LocalDateTime 反序列化
        objectMapper.registerModule(new JavaTimeModule());

        if ("".equals(shopJSon)) {
//            redis有无效数据。命中空字符。
            throw new HmdpException(ResultCodeEnum.shop_Not);
        }

        if (StringUtils.isBlank(shopJSon)) {
            return null;
        }

        RedisData redisData = objectMapper.readValue(shopJSon, RedisData.class);
// 反序列化 redisData 对象的 data 部分为 Shop 类
        Shop redisData_shop = objectMapper.convertValue(redisData.getData(), Shop.class);

//4.        redis命中后判断是否不为空。而且判断时间是否过期，

        // 转换为时间戳（秒数）

        if (redisData.getExpireTime().isAfter(LocalDateTime.now()) && StringUtils.isNotBlank(shopJSon)) {
//没有过期
            return redisData_shop;
        }
//        过期了
        String lockKey = RedisConstant.CACHE_Shop_Lock + id;
        boolean b = tryLock(lockKey);
        if (!b) {
            return redisData_shop;
        }
//锁上了。
        CACHE_REBUILD_EXECUTOR.submit(() -> {
            try {
                saveShop2Redis(id, 60 * 60L);
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            } finally {
                this.unLock(lockKey);
            }
        });

        return redisData_shop;
    }

}
